//Extracts the following for different numDataPoints
//0 - centroidAvg
//1 - centroidStdDev
//2 - centroidMin
//3 - centroidMax
//4 - rmsAvg
//5 - rmsStdDev
//6 - rmsMin
//7 - rmsMax
//8 - fft bin with highest fval (highest over all windows)
//9 - integer: id of the recorded example
//
//whenever an amplitude > threshold is detected from input, computed over
//numDataPoints 128-sample ffts that overlap by 64-samples
//
//outputs chuckId for every example at the end of th
//
//When it recieves a /playExample and an int, it plays the
//example with that chuckId once
//

"127.0.0.1" => string hostname;
OscSend xmit;
xmit.setHost( hostname, 6448 );
OscSend xmit2;
xmit2.setHost( hostname, 6448 );

OscRecv orec;
6450 => orec.port;
orec.listen();

//Custom objects
adc => FFT f =^ RMS rms => blackhole;
f =^ Centroid centroid => blackhole;
f =^ Flux flux => blackhole;
f =^ RollOff rolloff => blackhole;
UAnaBlob b;


//The audio examples
LiSa examples[500];
0 => int examplesRecorded;

//Set up bin stuff
128 => int FFT_SIZE;
FFT_SIZE => f.size;
Windowing.hamming(64) => f.window;

1::second / 1::samp => float SR;
SR/FFT_SIZE => float bin_width;

//constants
.25 => float threshold;
0 => int peakDetected; //flag if peak has been detected
now => time lastPeakTime;
100::samp => dur peakWindow;
10::samp => dur peakPollRate;
[5] @=> int numDataPoints[]; //list of numDataPoints values to try
1 => float rmsMultiplier; //so it isn't out of wek's range (this actually doesn't matter, leave it at 1)

0 => int currentlyAnalyzing; //flag if currently analyzing i.e. don't detect peak
0 => int num;
//Run the peak detector in parallel to set the peakDetected flag
spork ~peakDetector();
spork ~examplePlayer();

//Extract features and send via osc when peak detected.
while (true) {
    if (peakDetected) {
    
        <<<"Peak detected! Analyzing" + num>>>;
        1 +=> num;
        analyzeAndSend();
    }
    .1::second => now;
}

//When peak detected, compute min, max, avg, and std. dev for centroid and rms
//and send
fun void analyzeAndSend() {  
    1 => currentlyAnalyzing;
    
    xmit.startMsg( "/oscCustomFeatures", "fffffffffi");  
    adc => LiSa ls => f; //stick a lisa in there
    1600::samp => ls.duration;
    ls.record(1);
    
    analyzeNumDataPoints(5) @=> float result[];
    for (0 => int j; j < 9; j++) {
        result[j] => xmit.addFloat;
    }
    
    0 => ls.record;
    adc => f; //remove the lisa
    //route it to dac
    ls => dac;
    //add the lisa to the examples array
    ls => examples[examplesRecorded];
    1 +=> examplesRecorded;
    examplesRecorded => xmit.addInt;
    
    <<<"done">>>;
    0 => currentlyAnalyzing;
}

//Analyze data using the specified number of data points, return the float array corresponding to
//the osc message format described at top
fun float[] analyzeNumDataPoints(int numDataPoints) {
    float centroidTotal;
    float centroidMin;
    float centroidMax;
    float centroidStdDev;
    float rmsTotal;
    float rmsMin;
    float rmsMax;
    float rmsStdDev;
    float highestBin;
    float highestBinMagnitude;
    new float[numDataPoints] @=> float centroidData[];
    new float[numDataPoints] @=> float rmsData[];

    
    //advance 128 samples
    128::samp=> now;
    //get 1st centroid and rms
    rms.upchuck();
    rms.fval(0) +=> rmsTotal;
    rms.fval(0) => rmsMin => rmsMax => rmsData[0];
    centroid.upchuck();
    centroid.fval(0) +=> centroidTotal;
    centroid.fval(0) => centroidMin => centroidMax => centroidData[0];
    //do rest
    for (1 => int i; i < numDataPoints; i++) {
        64::samp => now;
        rms.upchuck();
        rms.fval(0) +=> rmsTotal;
        Math.max( rms.fval(0), rmsMax ) => rmsMax;
        Math.min( rms.fval(0), rmsMin ) => rmsMin;
        rms.fval(0) => rmsData[i];
        centroid.upchuck();
        centroid.fval(0) +=> centroidTotal;
        Math.max( centroid.fval(0), centroidMax ) => centroidMax;
        Math.min( centroid.fval(0), centroidMin ) => centroidMin;
        centroid.fval(0) => centroidData[i];
        //find highest bin
        for (1 => int j; j < FFT_SIZE; j++) {
            if (f.fval(j) > highestBinMagnitude) {
                f.fval(j) => highestBinMagnitude;
                j => highestBin;
            }
        }

    }
    //calculate
    rmsTotal / numDataPoints => float rmsAvg;
    centroidTotal / numDataPoints => float centroidAvg;
    //get std dev
    float rmsDevSquaredTotal;
    float centroidDevSquaredTotal;
    for (0 => int i; i < numDataPoints; i++) {
        (rmsData[i] - rmsAvg)*(rmsData[i] - rmsAvg) +=> rmsDevSquaredTotal; 
        (centroidData[i] - centroidAvg)*(centroidData[i] - centroidAvg) +=> centroidDevSquaredTotal;
    }
    Math.sqrt(rmsDevSquaredTotal / numDataPoints) => rmsStdDev;
    Math.sqrt(centroidDevSquaredTotal / numDataPoints) => centroidStdDev;
    
    float result[9];
    
	centroidAvg => result[0]; 
    centroidStdDev => result[1];
    centroidMin => result[2];
    centroidMax => result[3];
    rmsAvg * rmsMultiplier => result[4];
    rmsStdDev * rmsMultiplier => result[5];
    rmsMin * rmsMultiplier => result[6];
    rmsMax * rmsMultiplier => result[7];
    highestBin => result[8];
    
    return result;
}

//Plays back an example when it receives a /playExample
//message and an int specifying the id to play
fun void examplePlayer() {
    orec.event("/playExample,i") @=> OscEvent playEvent;

    while (true) {
        playEvent => now;
        while (playEvent.nextMsg() != 0) {
            playEvent.getInt() => int id;
            <<<"playing example " + id>>>;  
            //examples[id].playPos(0::samp); // set to beginning
            examples[id].play(1); //play it
            5::second => now;
            examples[id].play(0); //stop it
        }
    }
}

//Pretty inefficient but does the trick
//Looks for a peak in the last peakWindow 
fun void peakDetector() {
	while (true){
		if (adc.last() > threshold || adc.last() < -1 * threshold) {
			1 => peakDetected; // analyze
			now => lastPeakTime;	
		
		} else if (now > lastPeakTime + peakWindow) {
			0 => peakDetected;
		}

        peakPollRate => now; //This is silly; would be better do do a low-pass filter envelope and look for peaks there. But don't worry about it right now.

	}

}